<script setup lang="tsx">
import type { CSSProperties } from 'vue'
import { isClient, useEventListener, useWindowSize } from '@vueuse/core'
import { onActivated, onMounted, ref, shallowRef } from 'vue'
import { propTypes } from '../../../helpers/propTypes'

defineOptions({
  name: 'Sticky',
})
const props = defineProps({
  // 距离顶部或者底部的距离(单位px)
  offset: propTypes.number.def(0),
  // 设置元素的堆叠顺序
  zIndex: propTypes.number.def(999),
  // 定位方式，默认为(top)，表示距离顶部位置，可以设置为top或者bottom
  position: {
    type: String,
    validator(value: string) {
      return ['top', 'bottom'].includes(value)
    },
    default: 'top',
  },
})
const width = ref('auto' as string)
const height = ref('auto' as string)
const isSticky = ref(false)
const refSticky = shallowRef<HTMLElement>()
const scrollContainer = shallowRef<HTMLElement | Window>()
const { height: windowHeight } = useWindowSize()
onMounted(() => {
  height.value = `${refSticky.value?.getBoundingClientRect().height}px`

  scrollContainer.value = getScrollContainer(refSticky.value!, true)
  useEventListener(scrollContainer, 'scroll', handleScroll)
  useEventListener('resize', handleReize)
  handleScroll()
})
onActivated(() => {
  handleScroll()
})

function camelize(str: string): string {
  return str.replace(/-(\w)/g, (_, c) => (c ? c.toUpperCase() : ''))
}

function getStyle(
  element: HTMLElement,
  styleName: keyof CSSProperties,
): string {
  if (!isClient || !element || !styleName)
    return ''

  let key = camelize(styleName)
  if (key === 'float')
    key = 'cssFloat'
  try {
    const style = element.style[styleName as keyof CSSStyleDeclaration]
    if (style)
      return style as string
    const computed = document.defaultView?.getComputedStyle(element, '')
    return computed
      ? (computed[styleName as keyof CSSStyleDeclaration] as string)
      : ''
  }
  catch {
    return element.style[styleName as keyof CSSStyleDeclaration] as string
  }
}
function isScroll(el: HTMLElement, isVertical?: boolean): boolean {
  if (!isClient)
    return false
  const key = (
    {
      undefined: 'overflow',
      true: 'overflow-y',
      false: 'overflow-x',
    } as const
  )[String(isVertical)]!
  const overflow = getStyle(el, key)
  return ['scroll', 'auto', 'overlay'].some(s => overflow.includes(s))
}

function getScrollContainer(
  el: HTMLElement,
  isVertical: boolean,
): Window | HTMLElement | undefined {
  if (!isClient)
    return
  let parent = el
  while (parent) {
    if ([window, document, document.documentElement].includes(parent))
      return window
    if (isScroll(parent, isVertical))
      break

    parent = parent.parentNode as HTMLElement
  }
  console.log('Sticky Scroll Container:')
  console.log(parent)
  return parent
}

function handleScroll() {
  width.value = `${refSticky.value!.getBoundingClientRect().width!}px`
  if (props.position === 'top') {
    const offsetTop = refSticky.value?.getBoundingClientRect().top
    if (offsetTop !== undefined && offsetTop < props.offset) {
      sticky()
      return
    }
    reset()
  }
  else {
    const offsetBottom = refSticky.value?.getBoundingClientRect().bottom

    if (
      offsetBottom !== undefined
      && offsetBottom > windowHeight.value - props.offset
    ) {
      sticky()
      return
    }
    reset()
  }
}
function handleReize() {
  if (isSticky.value && refSticky.value)
    width.value = `${refSticky.value.getBoundingClientRect().width}px`
}
function sticky() {
  if (isSticky.value)
    return

  isSticky.value = true
}
function reset() {
  if (!isSticky.value)
    return

  width.value = 'auto'
  isSticky.value = false
}
</script>

<template>
  <div ref="refSticky" :style="{ height, zIndex }">
    <div
      class="sticky"
      :style="{
        top: position === 'top' ? `${offset}px` : '',
        bottom: position !== 'top' ? `${offset}px` : '',
        zIndex,
        position: isSticky ? 'fixed' : 'static',
        width,
        height,
      }"
    >
      <slot>
        <div>sticky</div>
      </slot>
    </div>
  </div>
</template>
